/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2022年11月29日
 * V4.0
 */
package com.jphenix.driver.udp;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;

/**
 * UDP服务器
 * 
 * com.jphenix.driver.udp.UDPServer
 * 
 * 2022-11-30 新增方法 hasSysCode(sysCode) 
 *            新增方法，获取token超时时间
 * 
 * @author mbg
 * 2022年11月29日
 */
@ClassInfo({"2024-07-25 12:40","UDP服务器"})
@BeanInfo({"udpserver","0","","","1"})
@Running({"1"})
public class UDPServer extends ABase {

	//#region 变量声明区
	private ReceiveThread       receiveThread    = null;              // 接收数据线程
	private TokenCheckThread    checkThread      = null;              // token信息超时检测线程
	private DatagramChannel     channel          = null;              // 通信类
	private Selector            selector         = null;              // 数据选择器
	private int                 tokenOutTime     = 300;               // token信息超时时间，单位：秒（默认5分钟）
	private int                 listenPort       = 0;                 // 本地监听端口
	private List<String>        localSysCodeList = new ArrayList<>(); // 本地服务代码序列
	private Map<String,String>  redirAddressMap  = new HashMap<>();   // 重定向地址容器 key:sysCode value:地址
	private Map<String,Integer> redirPortMap     = new HashMap<>();   // 重定向端口容器 key:sysCode value:端口
	private List<IReceiver>     receiverList     = new ArrayList<>(); // 接收处理器序列
	// 通信类容器 key:sysCode val:Map   valMap:key:token val:channel
	private Map<String,Map<String,MsgStateVO>> stateMap = new HashMap<>();
	//#endregion
	
	//#region TokenCheckThread 检测token信息超时线程
	/**
	 * 检测token信息超时线程
	 * @author mbg
	 * 2022年11月29日
	 */
	private class TokenCheckThread extends Thread {
		
		//#region 构造函数
		/**
		 * 构造函数
		 * @author mbg
		 */
		public TokenCheckThread() {
			super("UDPServer_TokenCheckThread");
		}
		//#endregion
		
		//#region run() 运行线程
		/**
		 * 覆盖方法
		 */
		public void run() {
			long now;
			while(true) {
				now = System.currentTimeMillis();
				// 系统代码迭代器
				Iterator<String> sysCodeKeys = stateMap.keySet().iterator();
				Iterator<String> tokenKeys;
				String sysCodeEle;
				String tokenEle;
				Map<String,MsgStateVO> tokenMap;
				MsgStateVO stateVO;
				while(sysCodeKeys.hasNext()) {
					sysCodeEle = sysCodeKeys.next();
					tokenMap   = stateMap.get(sysCodeEle);
					if(tokenMap==null) {
						stateMap.remove(sysCodeEle);
					}else {
						tokenKeys = tokenMap.keySet().iterator();
						while(tokenKeys.hasNext()) {
							tokenEle = tokenKeys.next();
							stateVO  = tokenMap.get(tokenEle);
							if(stateVO==null || stateVO.tokenTs+(tokenOutTime*60*1000)<now) {
								tokenMap.remove(tokenEle);
							}
						}
					}
				}
				try {
					Thread.sleep(10000);
				}catch(Exception e) {
					break;
				}
			}

		}
		//#endregion
	}
	//#endregion
	
	//#region ReceiveThread 接收数据线程
	/**
	 * 接收数据线程
	 * @author mbg
	 * 2022年11月28日
	 */
	private class ReceiveThread extends Thread {
		
		//#region 构造函数
		public ReceiveThread() {
			super("UDPServer_ReceiveThread");
		}
		//#endregion
		
		//#region run() 启动线程
		/**
		 * 启动线程
		 */
		public void run() {
			
			//#region 变量声明区
			Iterator<SelectionKey> it           = null;
			DatagramChannel        skc          = null;
			SelectionKey           sk           = null;
			ByteBuffer             bb           = ByteBuffer.allocate(1024);
			ByteBuffer             resBb        = null; // 返回消息缓存
			SocketAddress          recAddress   = null;
			MsgVO                  vo           = new MsgVO();
			String                 redirAddress = null;
			int                    redirPort    = 0;
			Map<String,MsgStateVO> sysStateMap;
			MsgStateVO             stateVO;
			//#endregion
			try {
				//#region 处理接收数据
				while(selector.select()>0) {
					it = selector.selectedKeys().iterator();
					while(it.hasNext()) {
						sk = it.next();
						it.remove();
						if(sk.isReadable()) {
							
							//#region 接收消息
							skc = (DatagramChannel)sk.channel();
							bb.clear();
							recAddress = skc.receive(bb);
							vo.parse(bb);
							if(vo.error) {
								resBb = MsgVO.respError("00","参数格式错误，没有获取到sysCode或token",null,null);
								if(resBb==null) {
									continue;
								}
								resBb.flip();
								skc.send(resBb,recAddress);
								continue;
							}
							//#endregion
							
							//#region 处理重定向消息
							if(!localSysCodeList.contains(vo.sysCode)) {
								redirAddress = redirAddressMap.get(vo.sysCode);
								redirPort    = redirPortMap.get(vo.sysCode);
								if(redirPort<1) {
									resBb = MsgVO.respError("E0","Unknow SysCode",vo.sysCode,vo.token);
									if(resBb==null) {
										continue;
									}
									resBb.flip();
									skc.send(resBb,recAddress);
									continue;
								}
								if(redirAddress==null || redirAddress.length()<1) {
									resBb = MsgVO.response(vo.sysCode,vo.token,redirPort);
								}else {
									resBb = MsgVO.response(vo.sysCode,vo.token,redirPort,redirAddress);
								}
								if(resBb==null) {
									continue;
								}
								resBb.flip();
								skc.send(resBb,recAddress);
								
							}
							//#endregion
							
							sysStateMap = stateMap.get(vo.sysCode);
							if(sysStateMap==null) {
								sysStateMap = new HashMap<>();
								stateMap.put(vo.sysCode,sysStateMap);
							}
							stateVO = sysStateMap.get(vo.token);
							if(stateVO==null) {
								resBb = MsgVO.respError("E2","Token Time Out",vo.sysCode,vo.token);
								if(resBb==null) {
									continue;
								}
								resBb.flip();
								skc.send(resBb,recAddress);
								continue;
							}
							
							stateVO.receive(skc,recAddress,vo);
							
							//#region 响应接收到的消息
							for(IReceiver ele:receiverList) {
								ele.receive(stateVO);
							}
							//#endregion
						}
					}
				}
				//#endregion
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		//#endregion
	}
	//#endregion
	
	//#region 构造函数
	/**
	 * 构造函数
	 * @author mbg
	 */
	public UDPServer() {
		super();
	}
	//#endregion
	
	//#region addReceiver(receiver) 添加接收处理器
	/**
	 * 添加接收处理器
	 * @param receiver 接收处理器
	 * 2022年11月29日
	 * @author mbg
	 */
	public void addReceiver(IReceiver receiver) {
		if(receiver==null || receiverList.contains(receiver)) {
			return;
		}
		receiver.setServer(this);
		receiverList.add(receiver);
	}
	//#endregion

	//#region setPort(port) 设置监听端口
	/**
	 * 设置监听端口
	 * @param port 监听端口
	 * 2022年11月29日
	 * @author mbg
	 */
	public void setPort(int port) {
		this.listenPort = port;
	}
	//#endregion
	
	//#region setTokenOutTime(outTime) 设置token信息超时时间（单位秒：默认5分钟）
	/**
	 * 设置token信息超时时间（单位秒：默认5分钟）
	 * @param outTime 超时时间（单位：秒）
	 * 2022年11月29日
	 * @author mbg
	 */
	public void setTokenOutTime(int outTime) {
		if(outTime>0) {
			tokenOutTime = outTime;
		}
	}
	//#endregion
	
	//#region getTokenOutTime 获取token信息超时时间（单位：秒）
	/**
	 * 获取token信息超时时间（单位：秒）
	 * @return token信息超时时间（单位：秒）
	 * 2022年11月30日
	 * @author mbg
	 */
	public int getTokenOutTime() {
		return tokenOutTime;
	}
	//#endregion
	
	//#region addLocalSysCode(sysCode)  添加本地服务代码
	/**
	 * 添加本地服务代码
	 * @param sysCode 服务代码
	 * 2022年11月29日
	 * @author mbg
	 */
	public void addLocalSysCode(String sysCode) {
		if(!localSysCodeList.contains(sysCode)) {
			localSysCodeList.add(sysCode);
		}
	}
	//#endregion
	
	//#region setRedirInfo(sysCode,address,port)  设置重定向信息
	/**
	 * 设置重定向信息
	 * @param sysCode  系统代码 
	 * @param address  重定向地址
	 * @param port     重定向端口
	 * 2022年11月29日
	 * @author mbg
	 */
	public void setRedirInfo(String sysCode,String address,int port) {
		redirAddressMap.put(sysCode,address);
		redirPortMap.put(sysCode,port);
	}
	//#endregion

	//#region getVO(sysCode,token) 获取指定的通信信息容器
	/**
	 * 获取指定的通信信息容器
	 * @param sysCode 系统代码
	 * @param token   token
	 * @return        通信信息容器
	 * 2022年11月29日
	 * @author mbg
	 */
	public MsgStateVO getVO(String sysCode,String token) {
		// 获取指定sysCode的容器
		Map<String,MsgStateVO> tokenMap = stateMap.get(sysCode);
		if(tokenMap==null) {
			return null;
		}
		return tokenMap.get(token);
	}
	//#endregion
	
	//#region regist(sysCode,token) 注册通信信息
	/**
	 * 注册通信信息
	 * 注意：只有后台先执行注册后，前台通过UDP才可以通信
	 * @param sysCode 系统代码
	 * @param token   token
	 * 2022年11月29日
	 * @author mbg
	 */
	public void regist(String sysCode,String token) {
		// 获取指定sysCode的容器
		Map<String,MsgStateVO> tokenMap = stateMap.get(sysCode);
		if(tokenMap==null) {
			tokenMap = new HashMap<>();
			stateMap.put(sysCode,tokenMap);
		}
		MsgStateVO stateVO = tokenMap.get(token);
		if(stateVO==null) {
			stateVO = new MsgStateVO();
			tokenMap.put(token,stateVO);
		}else {
			
		}
	}
	//#endregion
	
	//#region hasSysCode(sysCode) 是否存在指定系统代码
	/**
	 * 是否存在指定系统代码
	 * @param sysCode 系统代码
	 * @return        是否存在该系统代码
	 * 2022年11月30日
	 * @author mbg
	 */
	public boolean hasSysCode(String sysCode) {
		return stateMap.containsKey(sysCode);
	}
	//#endregion
	
	//#region start() 启动服务
	/**
	 * 启动服务
	 * @throws Exception 异常
	 * 2022年11月27日
	 * @author mbg
	 */
	public void start() throws Exception {
		stop(); // 启动前先尝试停止
		
		if(listenPort<2) {
			throw new Exception("The Listen Port Not Set Yet");
		}
		
		// 打开UDP频道管理类
		channel = DatagramChannel.open();
		// 切换到非阻塞模式
		channel.configureBlocking(false);
		// 绑定监听端口
		channel.bind(new InetSocketAddress(listenPort));
		// 选择器
		selector = Selector.open();
		// 注册选择器
		channel.register(selector,SelectionKey.OP_READ);
		
		// 启动监听线程
		receiveThread = new ReceiveThread();
		receiveThread.start();
		
		// 启动token检测线程
		checkThread = new TokenCheckThread();
		checkThread.start();
	}
	//#endregion
	
	//#region stop() 停止服务
	/**
	 * 停止服务
	 * 2022年11月29日
	 * @author mbg
	 */
	@SuppressWarnings("deprecation")
	public void stop() {
		if(channel==null) {
			return;
		}
		
		//#region 处理 checkThread
		if(checkThread!=null) {
			try {
				checkThread.stop();
			}catch(Exception e) {}
			checkThread = null;
		}
		//#endregion
		
		//#region 处理 receiveThread
		if(receiveThread!=null) {
			try {
				receiveThread.stop();
			}catch(Exception e) {}
			receiveThread = null;
		}
		//#endregion

		//#region 处理 channel
		try {
			channel.close();
		}catch(Exception e) {}
		channel = null;
		//#endregion

		//#region 处理 selector
		if(selector!=null) {
			try {
				selector.close();
			}catch(Exception e) {}
			selector = null;
		}
		//#endregion
	}
	//#endregion
}




